package club.jdiy.utils;

import javax.activation.MimetypesFileTypeMap;
import java.io.*;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.stream.Collectors;

import static java.io.File.separator;

/**
 * 文件系统操作工具类.
 *
 * @author ziquee(子秋) QQ号:39886616 主页: http://www.jdiy.club
 * @version 20210618
 */
@SuppressWarnings({"ALL"})
public final class FileUtils {

    private FileUtils() {
    }

    /**
     * 复制文件(或文件夹).
     * 此方法会自动建立要复制到的文件(或文件夹)所在路径所需的上层目录.
     *
     * @param fromFile  原文件 如：new File("/a.txt")
     * @param toFile    复制后文件 如：new File("/aaa/bbb/ccc/ddd/b.txt") (上层目录不存在，将会自动创建它们)
     * @param overwrite 指定当目标位置已经存在文件时，是否覆盖该文件.
     * @return 操作成功将返回true.应尽可能的检查返回状态，以判断操作是否成功.
     */
    public static boolean copy(File fromFile, File toFile, boolean overwrite) throws IOException {
        if (!fromFile.exists() || fromFile.equals(toFile)) {
            return false;
        } else if (fromFile.isFile()) {
            return cpFile(fromFile, toFile, overwrite);
        } else {
            return cpDir(fromFile, toFile, overwrite);
        }
    }


    /**
     * 删除文件（或文件夹）.
     * 如果file是一个文件夹，将删除此文件夹及其下的全部内容.
     *
     * @param file 要删除的文件（或文件夹）.
     */
    public static void delAll(File file) {
        if (file == null || !file.exists()) return;
        File[] fs;
        if (file.isDirectory() && (fs = file.listFiles()) != null) for (File f : fs) delAll(f);
        file.delete();
    }

    /**
     * 删除空目录.
     *
     * @param dir 如果dir所表示的目录下面没有任何内容,则删除之.
     * @see #delAll(File)
     */
    public synchronized static void delEmpty(File dir) {
        if (dir.isFile()) return;
        String[] s = dir.list();
        if (s == null || s.length < 1) dir.delete();//取消了删除空子级目录，因为当子目录或文件过多时占资源
    }

    /**
     * 清空目录. <br />
     * 此方法将删除目录下面的所有子文件夹及文件（当前的目录本身并不会删除）
     *
     * @param dir 要清空的目录.
     */
    public static void clearAll(File dir) {
        File[] fs;
        if (dir.isDirectory() && (fs = dir.listFiles()) != null) for (File f : fs) delAll(f);
    }


    /**
     * 移动文件(或文件夹).
     * 此方法会自动建立要移动到的文件(或文件夹)所在路径所需的上层目录.
     *
     * @param fromFile  原文件 如：new File("c:/a.txt")
     * @param toFile    移动后文件 如：new File("f:/aaa/bbb/ccc/ddd/b.txt") (上层目录不存在，将会自动创建它们)
     * @param overwrite 指定当目标位置已经存在文件时，是否覆盖该文件.
     * @return 操作成功将返回true.应尽可能的检查返回状态，以判断操作是否成功.
     */
    public static boolean move(File fromFile, File toFile, boolean overwrite) throws IOException {
        if (copy(fromFile, toFile, overwrite)) {
            delAll(fromFile);
            return true;
        }
        return false;
    }

    /**
     * 读取文本文件的内容.
     *
     * @param file    要读取的文件.  可以为任意文本类型的文件，例如：txt, log, html, bat, sh, js, css等.
     * @param charset 文本文件的编码类型. 例如： utf-8(默认值), gb2312等等.
     * @return 文本文件的内容.
     * @throws IOException
     * @see #read(File)
     * @see #write(File, String)
     * @see #write(File, String, Charset)
     */
    public static String read(File file, Charset charset) throws IOException {
        String contents;
        if (charset == null) charset = Charset.forName("utf-8");
        try (InputStreamReader read = new InputStreamReader(new FileInputStream(file), charset); BufferedReader reader = new BufferedReader(read);) {
            contents = reader.lines().map(text -> text + System.getProperty("line.separator")).collect(Collectors.joining());
        }
        return contents;
    }

    /**
     * 读取文本文件的内容. <br/>
     * 程序默认以utf-8的编码形式来读取文本文件的内容.
     * 如果文本文件不是utf-8的编码，请使用{@link #read(File, Charset)}方法指定编码.
     *
     * @param file 要读取的文件.  可以为任意文本类型的文件，例如：txt, log, html, bat, sh, js, css等.
     * @return 文本文件的内容.
     * @throws IOException
     * @see #read(File, Charset)
     * @see #write(File, String)
     * @see #write(File, String, Charset)
     */
    public static String read(File file) throws IOException {
        return read(file, Charset.forName("utf-8"));
    }


    /**
     * 将文本内容保存为文件. <br />
     * 在保存文件时，(如果上层路径不存在).系统将自动创建其上层目录路径.
     *
     * @param file    要保存的文件.
     * @param str     要保存的文本内容.
     * @param charset 指定文本文件编码方式. 例如：utf-8(默认值), gb2312 等等...
     * @see #write(File, String)
     * @see #read(File)
     * @see #read(File, Charset)
     */
    public static void write(File file, String str, Charset charset) throws IOException {
        if (!file.getParentFile().exists()) file.getParentFile().mkdirs();
        try (FileOutputStream fos = new FileOutputStream(file);
             OutputStreamWriter osw = new OutputStreamWriter(fos, charset);) {
            osw.write(str);
            osw.flush();
        }
    }

    /**
     * 将文本内容保存为文件. <br />
     * 程序默认以utf-8的编码形式保存文本文件，如要使用其它编码方式保存，
     * 请使用{@link #write(File, String, Charset)}方法.   <br />
     * 在保存文件时，(如果上层路径不存在).系统将自动创建其上层目录路径.
     *
     * @param file 要保存的文件.
     * @param str  要保存的文本内容.
     * @see #write(File, String, Charset)
     * @see #read(File)
     * @see #read(File, Charset)
     */
    public static void write(File file, String str) throws IOException {
        write(file, str, Charset.forName("utf-8"));
    }

    /**
     * 将InputStream输入流数据写入到文件。
     * @param file 要保存的文件
     * @param is 输入流
     * @throws IOException ex
     */
    public static void write(File file,InputStream is) throws IOException{
        try(FileOutputStream fos = new FileOutputStream(file)) {
            int len;
            byte[] buffer = new byte[1024];
            while ((len = is.read(buffer)) != -1) {
                fos.write(buffer, 0, len);
            }
        }
    }

    /**
     * 返回指定路径下的文件（夹）所占用的空间字节大小.
     *
     * @param file 文件（夹）
     * @return 该路径下的文件(夹)所占用的空间字节大小.
     */
    public static long size(File file) {
        if (!file.exists()) return 0L;
        if (file.isFile()) return file.length();
        File[] fs = file.listFiles();
        return (fs != null) ? Arrays.stream(fs).mapToLong(FileUtils::size).sum() : 0L;
    }

    /**
     * 以classpath为相对根路径，获取资源.
     *
     * @param relativePath 相对路径地址，例如：<br />
     *                     a.txt    -  %classpath%/a.txt<br />
     *                     ./a.txt  -  同上              <br />
     *                     /a.txt   -  同上              <br />
     *                     ../a.txt -  %classpath%的上级路径下的a.txt <br />
     *                     abc/a.txt-  %classpath%/abc/a.txt
     * @return 该资源的URL对象，可以通过 <b>returnurl.getPath()</b>来获取该资源的真实物理路径(returnurl即此方法返回的URL对象)。
     * @throws java.net.MalformedURLException MalformedURLException
     */
    public static URL getResource(String relativePath) throws MalformedURLException {
        if (relativePath == null) {
            relativePath = "./";
        } else if (relativePath.startsWith("/")) {
            relativePath = "." + relativePath;
        } else if (!relativePath.startsWith("./") && !relativePath.startsWith("../")) {
            relativePath = "./" + relativePath;
        }
        @SuppressWarnings("ALL")
        String classAbsPath = FileUtils.class.getClassLoader().getResource("").toString();
        String parentStr = relativePath.substring(0, relativePath.lastIndexOf("../") + 3);
        relativePath = relativePath.substring(relativePath.lastIndexOf("../") + 3);
        int containSum = StringUtils.containSum(parentStr, "../");
        for (int i = 0; i < containSum; i++)
            classAbsPath = classAbsPath.substring(0, classAbsPath.lastIndexOf("/", classAbsPath.length() - 2) + 1);

        return new URL(classAbsPath + relativePath);
    }

    /**
     * 获取文件的contentType值。
     * 该方式支持本地文件，也支持http/https远程文件
     * @param fileUrl 文件absolutePath或者url地址.
     */
    public static String getContentType(String fileUrl) {
        String contentType = null;
        try {
            contentType = new MimetypesFileTypeMap().getContentType(new File(fileUrl));
        } catch (Exception e) {
            e.printStackTrace();
        }
        return contentType;
    }

    /**
     * 根据文件大小自动格式化为以Byte,Kb, Mb, Gb等单位的字符串显示.
     * 系统采用1024进位.
     *
     * @param size 文件（夹）的字节大小
     * @return 返回该字节大小格式化后的Byte, Kb, Mb, Gb等单位的字符串
     */
    public static String sizeStr(long size) {
        double size0;
        String s = "Byte";
        if (size < 1024 * 1024 && size > 1024) {
            size0 = size / 1024.0;
            s = "KB";
        } else if (size < 1024 * 1000 * 1024 && size > 1024 * 1024) {
            size0 = size / (1024 * 1024.0);
            s = "MB";
        } else if (size < 1024L * 1024 * 1024 * 1024 && size > 1024 * 1024 * 1024) {
            size0 = size / (1024 * 1024 * 1024.0);
            s = "GB";
        } else {
            size0 = size;
        }
        size0 = Math.round(size0 * 100.0) / 100.0;
        String ss = String.valueOf(size0);
        if (ss.lastIndexOf(".") == ss.length() - 2) {
            ss += "0";
        }
        return ss + " " + s;
    }

    /**
     * 获取文件（夹）的占用空间大小，并格式化为Byte,Kb, Mb, Gb等单位的字符串显示.
     * 系统采用1024进位.
     *
     * @param file 要获取大小的文件(或文件夹).
     * @return 返回该文字的字节大小字符串
     */
    public static String sizeStr(File file) {
        return sizeStr(size(file));
    }


    public static String getTempDirectoryPath() {
        return System.getProperty("java.io.tmpdir");
    }
    public static File getTempDirectory() {
        return new File(getTempDirectoryPath());
    }


    private static boolean cpFile(File fromFile, File toFile, boolean overWrite) throws IOException {
        if (!overWrite && toFile.exists()) {
            return false;
        }
        int byteread;
        toFile.getParentFile().mkdirs();
        InputStream inStream = new FileInputStream(fromFile.getPath()); //读入原文件
        FileOutputStream fs = new FileOutputStream(toFile.getPath());
        byte[] buffer = new byte[1444];
        while ((byteread = inStream.read(buffer)) != -1)
            fs.write(buffer, 0, byteread);

        inStream.close();
        fs.close();
        return true;
    }

    private static boolean cpDir(File fromFile, File toFile, boolean overWrite) throws IOException {
        if (!overWrite && toFile.exists()) {
            return false;
        }
        toFile.mkdirs();
        File[] files = fromFile.listFiles();
        boolean isOk = true;
        if (files != null) {
            for (File file : files) {
                if (file.isFile()) {
                    if (!cpFile(file, new File(toFile.getPath() + separator + file.getName()), true))
                        isOk = false;
                } else {
                    if (!cpDir(file, new File(toFile.getPath() + separator + file.getName()), true))
                        isOk = false;
                }
            }
        }
        return isOk;
    }
}
